package org.imaginedays.androidall.http;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.imaginedays.androidall.error.AndroidAllError;
import org.imaginedays.androidall.error.AndroidAllException;
import org.imaginedays.androidall.error.AndroidAllParseException;
import org.imaginedays.androidall.types.BaseType;
import org.imaginedays.androidall.types.UploadFile;
import org.imaginedays.androidall.types.User;
import org.imaginedays.androidall.xml.parsers.AbstractParser;
import org.imaginedays.androidall.xml.parsers.Parser;

import android.text.TextUtils;

/**
 * 抽象abstract httpApi
 * 
 * @author imaginedays
 * 
 */
public abstract class AbstractHttpApi implements HttpApi {

	protected static final Logger LOG = Logger.getLogger(AbstractHttpApi.class
			.getCanonicalName());

	private static final String DEFAULT_CLIENT_VERSION = "org.imaginedays.androidall";

	private static final String CLIENT_VERSION_HEADER = "User-Agent";

	private final DefaultHttpClient mHttpClient;

	private final String mClientVersion;

	private static final int TIMEOUT = 60;

	public AbstractHttpApi(DefaultHttpClient mHttpClient, String mClientVersion) {
		super();
		this.mHttpClient = mHttpClient;
		if (mClientVersion != null) {
			this.mClientVersion = mClientVersion;
		} else {
			this.mClientVersion = DEFAULT_CLIENT_VERSION;
		}

	}

	/**
	 * 执行http 请求 返回 baseType
	 * 
	 * @param httpRequest
	 * @param parser
	 * @return
	 * @throws AndroidAllParseException
	 * @throws AndroidAllException
	 * @throws IOException
	 */
	public BaseType executeHttpRequest(HttpRequestBase httpRequest,
			Parser<? extends BaseType> parser) throws AndroidAllParseException,
			AndroidAllException, IOException {

		if (DEBUG)
			LOG.log(Level.FINE, "doHttpRequest: " + httpRequest.getURI());

		HttpResponse response = executeHttpRequest(httpRequest);
		if (DEBUG)
			LOG.log(Level.FINE, "executed HttpRequest for: "
					+ httpRequest.getURI().toString());

		int statusCode = response.getStatusLine().getStatusCode();
		switch (statusCode) {
		case 200:
			InputStream is = response.getEntity().getContent();
			try {
				return parser.parse(AbstractParser.createXmlPullParser(is));
			} finally {
				is.close();
			}

		case 400:
			if (DEBUG)
				LOG.log(Level.FINE, "HTTP Code: 400");
			throw new AndroidAllException(response.getStatusLine().toString(),
					EntityUtils.toString(response.getEntity()));

		case 401:
			response.getEntity().consumeContent();
			if (DEBUG)
				LOG.log(Level.FINE, "HTTP Code: 401");
			throw new AndroidAllException(response.getStatusLine().toString());

		case 404:
			response.getEntity().consumeContent();
			if (DEBUG)
				LOG.log(Level.FINE, "HTTP Code: 404");
			throw new AndroidAllException(response.getStatusLine().toString());

		case 500:
			response.getEntity().consumeContent();
			if (DEBUG)
				LOG.log(Level.FINE, "HTTP Code: 500");
			throw new AndroidAllException(
					"Application is down. Try again later.");

		default:
			if (DEBUG)
				LOG.log(Level.FINE, "Default case for status code reached: "
						+ response.getStatusLine().toString());
			response.getEntity().consumeContent();
			throw new AndroidAllException("Error connecting to Server: "
					+ statusCode + ". Try again later.");
		}

	}

	/**
	 * execute() an httpRequest catching exceptions and returning null instead.
	 * 
	 * @param httpRequest
	 * @return
	 * @throws IOException
	 */
	public HttpResponse executeHttpRequest(HttpRequestBase httpRequest)
			throws IOException {
		if (DEBUG)
			LOG.log(Level.FINE, "executing HttpRequest for: "
					+ httpRequest.getURI().toString());
		try {
			mHttpClient.getConnectionManager().closeExpiredConnections();
			return mHttpClient.execute(httpRequest);
		} catch (IOException e) {
			httpRequest.abort();
			throw e;
		}
	}

	@Override
	public BaseType doHttpRequest(HttpRequestBase httpRequest,
			Parser<? extends BaseType> parser) throws AndroidAllParseException,
			AndroidAllException, IOException {
		return executeHttpRequest(httpRequest, parser);
	}

	@Override
	public String doHttpPost(String url, NameValuePair... nameValuePairs)
			throws AndroidAllParseException, AndroidAllException, IOException {
		if (DEBUG)
			LOG.log(Level.FINE, "doHttpPost: " + url);
		HttpPost httpPost = createHttpPost(url, nameValuePairs);

		HttpResponse response = executeHttpRequest(httpPost);
		if (DEBUG)
			LOG.log(Level.FINE, "executed HttpRequest for: "
					+ httpPost.getURI().toString());

		switch (response.getStatusLine().getStatusCode()) {
		case 200:
			try {
				return EntityUtils.toString(response.getEntity());
			} catch (ParseException e) {
				throw new AndroidAllParseException(e.getMessage());
			}

		case 401:
			response.getEntity().consumeContent();
			throw new AndroidAllException(response.getStatusLine().toString());

		case 404:
			response.getEntity().consumeContent();
			throw new AndroidAllException(response.getStatusLine().toString());

		default:
			response.getEntity().consumeContent();
			throw new AndroidAllException(response.getStatusLine().toString());
		}
	}

	@Override
	public HttpGet createHttpGet(String url, NameValuePair... nameValuePairs) {
		if (DEBUG)
			LOG.log(Level.FINE, "creating HttpGet for: " + url);
		String query = URLEncodedUtils.format(stripNulls(nameValuePairs),
				HTTP.UTF_8);
		HttpGet httpGet = new HttpGet(url + "?" + query);
		httpGet.addHeader(CLIENT_VERSION_HEADER, mClientVersion);
		if (DEBUG)
			LOG.log(Level.FINE, "Created: " + httpGet.getURI());
		return httpGet;
	}

	@Override
	public HttpPost createHttpPost(String url, NameValuePair... nameValuePairs) {
		if (DEBUG)
			LOG.log(Level.FINE, "creating HttpPost for: " + url);
		HttpPost httpPost = new HttpPost(url);
		httpPost.addHeader(CLIENT_VERSION_HEADER, mClientVersion);
		try {
			httpPost.setEntity(new UrlEncodedFormEntity(
					stripNulls(nameValuePairs), HTTP.UTF_8));
		} catch (UnsupportedEncodingException e1) {
			throw new IllegalArgumentException(
					"Unable to encode http parameters.");
		}
		if (DEBUG)
			LOG.log(Level.FINE, "Created: " + httpPost);
		return httpPost;
	}

	private List<NameValuePair> stripNulls(NameValuePair... nameValuePairs) {
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		for (int i = 0; i < nameValuePairs.length; i++) {
			NameValuePair param = nameValuePairs[i];
			if (param.getValue() != null) {
				if (DEBUG)
					LOG.log(Level.FINE, "Param: " + param);
				params.add(param);
			}
		}
		return params;
	}

	/**
	 * Create a thread-safe client. This client does not do redirecting, to
	 * allow us to capture correct "error" codes.
	 * 
	 * @return HttpClient
	 */
	public static final DefaultHttpClient createHttpClient() {
		// Sets up the http part of the service.
		final SchemeRegistry supportedSchemes = new SchemeRegistry();

		// Register the "http" protocol scheme, it is required
		// by the default operator to look up socket factories.
		final SocketFactory sf = PlainSocketFactory.getSocketFactory();
		supportedSchemes.register(new Scheme("http", sf, 80));
		supportedSchemes.register(new Scheme("https", SSLSocketFactory
				.getSocketFactory(), 443));

		// Set some client http client parameter defaults.
		final HttpParams httpParams = createHttpParams();
		HttpClientParams.setRedirecting(httpParams, false);

		final ClientConnectionManager ccm = new ThreadSafeClientConnManager(
				httpParams, supportedSchemes);
		return new DefaultHttpClient(ccm, httpParams);
	}

	/**
	 * Create the default HTTP protocol parameters.
	 */
	private static final HttpParams createHttpParams() {
		final HttpParams params = new BasicHttpParams();

		// Turn off stale checking. Our connections break all the time anyway,
		// and it's not worth it to pay the penalty of checking every time.
		HttpConnectionParams.setStaleCheckingEnabled(params, false);

		HttpConnectionParams.setConnectionTimeout(params, TIMEOUT * 1000);
		HttpConnectionParams.setSoTimeout(params, TIMEOUT * 1000);
		HttpConnectionParams.setSocketBufferSize(params, 8192);

		return params;
	}

	/**
	 * @param post
	 */
	private static void addRequetHeader(HttpRequestBase requestMethod) {
		// 添加http 请求头
		requestMethod.addHeader("uId", User.UID);
		requestMethod.addHeader("uName", User.UNAME);
	}

	public static String upload(String uploadFile, String uploadUrl)
			throws Exception {
		File file = new File(uploadFile);
		InputStream is = null;
		HttpClient httpClient = null;
		is = new FileInputStream(uploadFile);
		// 发送任务数据包
		HttpParams httpParameters = new BasicHttpParams();
		HttpConnectionParams.setConnectionTimeout(httpParameters, 20000);
		HttpConnectionParams.setSoTimeout(httpParameters, 20000);

		httpClient = new DefaultHttpClient(httpParameters);
		HttpContext localContext = new BasicHttpContext();
		HttpPost httpPost = new HttpPost(uploadUrl);
		InputStreamEntity entity = new InputStreamEntity(is, file.length());
		httpPost.setEntity(entity);
		HttpResponse response = httpClient.execute(httpPost, localContext);
		// 接收响应结果
		BufferedReader reader = null;
		String data = "";
		try {
			reader = new BufferedReader(new InputStreamReader(response
					.getEntity().getContent(), "UTF-8"));
			String line = null;
			while ((line = reader.readLine()) != null) {
				data += line;
			}
		} finally {
			if (reader != null) {
				reader.close();
			}
			if (is != null) {
				is.close();
			}
		}

		return data;
	}

	@Override
	public HttpURLConnection createHttpURLConnectionPost(URL url,
			String boundary) throws IOException {
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setDoInput(true);
		conn.setDoOutput(true);
		conn.setUseCaches(false);
		conn.setConnectTimeout(TIMEOUT * 1000);
		conn.setRequestMethod("POST");

		conn.setRequestProperty(CLIENT_VERSION_HEADER, mClientVersion);
		conn.setRequestProperty("Connection", "Keep-Alive");
		conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="
				+ boundary);
		return conn;
	}

	public HttpURLConnection createHttpURLConnectionGet(URL url, String boundary)
			throws IOException {
		String MULTIPART_FORM_DATA = "multipart/form-data";
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setDoInput(true);
		conn.setDoOutput(true);
		conn.setUseCaches(false);
		conn.setRequestMethod("GET");
		conn.setRequestProperty("Connection", "Keep-Alive");
		conn.setRequestProperty("Charset", "UTF-8");
		conn.setRequestProperty("Content-Type", MULTIPART_FORM_DATA
				+ ";boundary=" + boundary);
		return conn;
	}

	/**
	 * 添加图片与参数的get请求
	 * 
	 * @param urlPath
	 * @param params
	 * @param file
	 * @param parser
	 * @return
	 * @throws Exception
	 */
	@Override
	public BaseType accessServerByGetWithImg(String urlPath,
			Map<String, String> params, UploadFile file,
			Parser<? extends BaseType> parser) throws Exception {
		BaseType type = null;
		InputStream is = null;
		String BOUNDARY = "--------7d4a6d158c9";
		URL url = new URL(urlPath);
		HttpURLConnection conn = createHttpURLConnectionGet(url, BOUNDARY);
		addHeader(conn);
		StringBuilder sb = new StringBuilder();
		for (Map.Entry<String, String> entry : params.entrySet()) {
			sb.append("--");
			sb.append(BOUNDARY);
			sb.append("\r\n");
			sb.append("Content-Disposition:form-data;name=\"" + entry.getKey()
					+ "\"\r\n\r\n");
			sb.append(entry.getValue());
			sb.append("\r\n");
		}
		DataOutputStream outStream = new DataOutputStream(
				conn.getOutputStream());
		outStream.write(sb.toString().getBytes());

		// 上传文件数据
		StringBuilder split = new StringBuilder();
		split.append("--");
		split.append(BOUNDARY);
		split.append("\r\n");
		String fileName = URLEncoder.encode(file.getFilname(), "utf-8");
		split.append("Content-Disposition: form-data;name=\""
				+ file.getParameterName() + "\";filename=\"" + fileName
				+ "\"\r\n");
		split.append("Content-Type: " + file.getContentType() + "\r\n\r\n");
		outStream.write(split.toString().getBytes());

		int bufferSize = 1024;
		byte[] buffer = new byte[bufferSize];
		int length = -1;
		if (file.getInStream() != null) {
			while ((length = file.getInStream().read(buffer)) != -1) {
				outStream.write(buffer, 0, length);
			}
		}
		outStream.write("\r\n".getBytes());
		byte[] end_data = ("--" + BOUNDARY + "--\r\n").getBytes();// 数据结束标志
		outStream.write(end_data);
		outStream.flush();
		int respCode = conn.getResponseCode();

		if (respCode == 200) {
			is = conn.getInputStream();
			try {
				type = parser.parse(AbstractParser.createXmlPullParser(is));
			} finally {
				if (is != null) {
					is.close();
				}
			}
		} else {
			// resp.setMessage("失败，错误返回码为:" + respCode);
		}
		return type;
	}

	/**
	 * 一般参数访问的get请求
	 * 
	 * @param params
	 * @param serverUrl
	 * @param parser
	 * @return
	 */
	@Override
	public BaseType accessServerByGet(String serverUrl,
			HashMap<String, String> params, Parser<? extends BaseType> parser) {

		InputStream is = null;
		BaseType type = null;
		try {
			// 处理参数
			StringBuilder sb = new StringBuilder("");
			if (params != null && !params.isEmpty()) {
				for (Map.Entry<String, String> entry : params.entrySet()) {
					if (entry.getValue() == null) {
						continue;
					}
					sb.append(entry.getKey())
							.append('=')
							.append(URLEncoder.encode(entry.getValue(), "UTF-8"))
							.append('&');
				}
				sb.deleteCharAt(sb.length() - 1);
			}
			byte[] data = sb.toString().getBytes();

			// 建立连接
			URL url = new URL(serverUrl);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			addHeader(conn);
			conn.setRequestMethod("GET");
			conn.setConnectTimeout(5000);
			conn.setDoOutput(true);
			OutputStream outStream = conn.getOutputStream();
			outStream.write(data);
			// 读取数据
			int respCode = conn.getResponseCode();
			if (respCode == 200) {
				is = conn.getInputStream();
				try {
					type = parser.parse(AbstractParser.createXmlPullParser(is));
				} finally {
					if (is != null) {
						is.close();
					}
				}
			} else {
				// object.setMessage("respCode" + respCode);
				// object.setObject(null);
			}
			outStream.flush();
			outStream.close();

		} catch (Throwable th) {
			// object.setMessage("程序运行中出现网络连接异常!");
			// object.setMessage("exception:" + th.getMessage());
			// object.setObject(null);
		}
		return type;
	}

	@Override
	public BaseType accessServerByPost(String url, Map<String, String> params,
			Parser<? extends BaseType> parser) throws AndroidAllError,
			AndroidAllParseException {
		HttpURLConnection conn = null;
		BaseType type = null;
		InputStream is = null;
		try {
			StringBuffer paramStr = new StringBuffer();
			for (Iterator iter = params.entrySet().iterator(); iter.hasNext();) {
				Entry element = (Entry) iter.next();
				paramStr.append(element.getKey().toString());
				paramStr.append("=");
				paramStr.append(URLEncoder.encode(
						element.getValue().toString(), "UTF-8"));
				paramStr.append("&");
			}

			if (paramStr.length() > 0) {
				paramStr = paramStr.deleteCharAt(paramStr.length() - 1);
			}
			URL requestUrl = new URL(url);
			conn = (HttpURLConnection) requestUrl.openConnection();
			conn.setRequestMethod("POST");
			conn.setReadTimeout(6000);
			conn.setDoOutput(true);
			byte[] b = params.toString().getBytes();
			conn.getOutputStream().write(b, 0, b.length);
			conn.getOutputStream().flush();
			conn.getOutputStream().close();
			is = conn.getInputStream();
			type = parser.parse(AbstractParser.createXmlPullParser(is));

		} catch (IOException e) {
		} finally {
			if (conn != null) {
				conn.disconnect();
			}

			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return type;
	}

	@Override
	public BaseType httpPostWithImg(String url, String imagePath,
			HashMap<String, String> params, Parser<? extends BaseType> parser)
			throws Exception {
		StringBuffer sb = new StringBuffer("");
		MultipartEntity mpEntity = new MultipartEntity();
		if (params != null && !params.isEmpty()) {
			for (Map.Entry<String, String> entry : params.entrySet()) {
				try {
					StringBody par = new StringBody(entry.getValue());
					mpEntity.addPart(entry.getKey(), par);
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				}
			}
		}
		// 图片
		if (!TextUtils.isEmpty(imagePath)) {
			FileBody file = new FileBody(new File(imagePath));
			mpEntity.addPart("photo", file);
		}

		// 使用HttpPost对象设置发送的URL路径
		HttpPost post = new HttpPost(url);
		addRequetHeader(post);
		// 发送请求体
		post.setEntity(mpEntity);
		return executeHttpRequest(post, parser);
	}

	@Override
	public BaseType accessServerByPostWithImg(String url, String imagePath,
			Map<String, String> params, Parser<? extends BaseType> parser)
			throws AndroidAllError, AndroidAllParseException {
		return null;
	}

	final static void addHeader(HttpURLConnection conn) {
		for (Map.Entry<String, String> entry : commonParams.entrySet()) {
			if (entry.getValue() != null) {
				conn.addRequestProperty(entry.getKey(), entry.getValue());
			}
		}
	}

	public static HashMap<String, String> commonParams = new HashMap<String, String>();

	public static void initCommonParams() {

		// 系统信息
		commonParams.put("uId", User.UID);
		commonParams.put("uName", User.UNAME);

	}

}
